package com.dycong.common.reflect;

import lombok.extern.log4j.Log4j;
import sun.misc.Unsafe;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Created by Yu on 2015/3/2.
 */
@Log4j
public class ClassUtil {

    public static Object getFieldValue(Object target, Field field) {
        try {
            boolean accessible = field.isAccessible();
            field.setAccessible(true);
            Object result = field.get(target);
            field.setAccessible(accessible);
            return result;
        } catch (Exception e) {
            throw new IllegalStateException("获取对象的属性[" + field.getName() + "]值失败", e);
        }
    }

    public static Object getFieldValue(Object target, String fieldName) {
        return getFieldValue(target, getField(target.getClass(), fieldName).orElse(null));
    }

    public static Optional<Field> getField(Class<?> targetClass, String fieldName) {
        AssertUtil.notNull(targetClass, fieldName);
        return getAllDeclaredField(targetClass)
                .stream()
                .filter(f -> f.getName().equals(fieldName))
                .findAny();
    }

    public static List<Field> getAllDeclaredField(Class<?> targetClass,
                                                  String... excludeFieldNames) {
        List<Field> fields = new ArrayList<>();
        for (Field field : targetClass.getDeclaredFields()) {
            if (Arrays.asList(excludeFieldNames).contains(field.getName())) {
                continue;
            }
            fields.add(field);
        }
        Class<?> parentClass = targetClass.getSuperclass();
        if (parentClass != Object.class) {
            fields.addAll(getAllDeclaredField(parentClass, excludeFieldNames));
        }
        return fields;
    }

    public static Optional<Class> getFieldType(Class<?> targetClass, String fieldName) {
        return getField(targetClass, fieldName)
                .map(Field::getType);
    }


    public static boolean isNumberType(Class<?> c) {
        return c == int.class || c == long.class || c == double.class || c == float.class
                || Number.class.isAssignableFrom(c);
    }

    public static boolean isEnumType(Class<?> c) {
        return Enum.class.isAssignableFrom(c);
    }


    public static boolean isBooleanType(Class<?> c) {
        return c == Boolean.class || c == boolean.class;
    }


    public static <T> Optional<T> newInstance(Class<T> type, Object... values) {
        try {
            T t;
            if (type == int.class || type == Integer.class) {
                switch (values.length) {
                    case 0:
                        t = (T) Integer.valueOf(0);
                        break;
                    case 1:
                        t = (T) Integer.valueOf(values[0].toString());
                        break;
                    default:
                        t = null;
                }
            } else if (type == long.class || type == Long.class) {
                switch (values.length) {
                    case 0:
                        t = (T) Long.valueOf(0);
                        break;
                    case 1:
                        t = (T) Long.valueOf(values[0].toString());
                        break;
                    default:
                        t = null;
                }
            } else if (type == double.class || type == Double.class) {
                switch (values.length) {
                    case 0:
                        t = (T) Double.valueOf(0);
                        break;
                    case 1:
                        t = (T) Double.valueOf(values[0].toString());
                        break;
                    default:
                        t = null;
                }
            } else if (type == float.class || type == Float.class) {
                switch (values.length) {
                    case 0:
                        t = (T) Float.valueOf(0);
                        break;
                    case 1:
                        t = (T) Float.valueOf(values[0].toString());
                        break;
                    default:
                        t = null;
                }
            } else if (Enum.class.isAssignableFrom(type)) {
                if (values.length == 1) {
                    Class<? extends Enum> clazz = (Class<? extends Enum>) type;
                    t = (T) Enum.valueOf(clazz, values[0].toString());
                } else {
                    t = null;
                }
            } else if (isBooleanType(type)) {
                t = (T) (values == null || values.length == 0 || values[0] == null
                        ? Boolean.FALSE : Boolean.valueOf((values[0]).toString()));
            } else {
                if (values.length == 0) {
                    t = type.newInstance();
                } else {
                    Class<?>[] cs = Arrays.stream(values)
                            .map(Object::getClass)
                            .collect(Collectors.toList())
                            .toArray(new Class[values.length]);
                    Constructor<T> constructor = type.getConstructor(cs);
                    constructor.setAccessible(true);
                    t = constructor.newInstance(values);
                }
            }
            return Optional.ofNullable(t);
        } catch (Throwable throwable) {
            log.error("", throwable);
            return Optional.empty();
        }
    }


    public static long sizeOf(Object o) {
        Unsafe u = getUnsafe();
        HashSet<Field> fields = new HashSet<>();
        Class c = o.getClass();
        while (c != Object.class) {
            for (Field f : c.getDeclaredFields()) {
                if ((f.getModifiers() & Modifier.STATIC) == 0) {
                    fields.add(f);
                }
            }
            c = c.getSuperclass();
        }

        // get offset
        long maxSize = fields.stream().mapToLong(u::objectFieldOffset).max().orElse(0l);

        return ((maxSize / 8) + 1) * 8;   // padding
    }


    public static Unsafe getUnsafe() {
        try {
            Field f = Unsafe.class.getDeclaredField("theUnsafe");
            f.setAccessible(true);
            return (Unsafe) f.get(null);
        } catch (RuntimeException e) {
            throw e;
        } catch (Throwable throwable) {
            throw new RuntimeException(throwable);
        }
    }


    public static  <T> Class<T> getTClass(Class<?> currentClazz)
    {
        Class<T> tClass = (Class<T>)((ParameterizedType)currentClazz.getGenericSuperclass()).getActualTypeArguments()[0];
        return tClass;
    }
}
